#! /usr/local/bin/lua
--[[
Lua script for OpenGLES2 which generates 
core interface
core proxy functions which call the core interface fuctions
mock class which is mocked with gmock
As gmock is default limited to ten parameters then also parameter wrappers for functions which
take more than ten.
--]]

local Mocking_details = {}

--[[
gmock and maybe tuple has a limitation of 10 types so a struct is required for more params
this is where they are stored
--]]

local extraParamStructs ={}

function printf(...) 
	io.write(string.format(unpack(arg))) 
end

function ltrim(s)
  return (s:gsub("^%s*", ""))
end

function rtrim(s)
  local n = #s
  while n > 0 and s:find("^%s", n) do n = n - 1 end
  return s:sub(1, n)
end

function trim_string(s)
	return rtrim(ltrim(s))
end

local function headerGuard(fileHandle,guardName)
	fileHandle:write("\#ifndef " ..guardName .. " \n\#\tdefine " ..guardName .. "\n")
end

local function includeHeader(fileHandle,includeFile,angleBracket)
	local str = "\#\tinclude "
	local startBracket = "\"" 
	local endBracket = startBracket
	
	if(angleBracket == true)then 
		startBracket = "<"
		endBracket =">"
	end
	
	str = str ..startBracket ..includeFile ..endBracket .."\n"
	fileHandle:write(str)
end

--local 
function split_str(str)
	local delim =","
	local start =1
	local entries ={}
	while (true) do
		local delimStart, delimEnd = string.find(str,delim,start)
		if(delimStart)then
			local entry = trim_string(string.sub(str,start,delimStart-1))
			table.insert(entries,entry)
			start = delimEnd+1
		else
			local entry = trim_string(string.sub(str,start,#str))
			local s1,validStr,s2 = string.match(entry,"(%s*)(.*)(%s*)")
			if(validStr and validStr ~= "void") then table.insert(entries,validStr) end
			break
		end
	end
	return entries
end

--local
function splitTypesAndNames(params)
	local functionParams =
	{
	}

	local keywords = 
	{
		GLvoid =1,
		GLchar =1,
		GLenum =1,
		GLboolean =1,
		GLbitfield =1,
		GLbyte =1,
		GLshort =1,
		GLint =1,
		GLsizei =1,
		GLubyte =1,
		GLushort =1,
		GLuint =1,
		GLfloat =1,
		GLclampf =1,
		GLfixed =1,
		const =1,
		GLeglImageOES
	}


	local noNameCounter = 0
	for i , v in pairs(params) do
		local last_symbol, arr = string.match(v,"(%a*)(%s*%[*%d*%]*)$")
		local varType
		local varName

		if(last_symbol == "" or last_symbol == nil )then
			varType = v
		else		
			if	(keywords[trim_string(last_symbol)] == 1)then
				if(arr == "")then
					varType = v
				else
					local arrIndex = string.find(v,"%[")
					varType = trim_string( string.sub(v,1,arrIndex-1 ) )
				end
			else	
				varType = string.sub(v,1,string.len(v)-string.len(last_symbol) -string.len(arr) )
				varName = last_symbol
			end
		end
		
		if(varName == nil) then
			varName = "noName"..noNameCounter
			noNameCounter = noNameCounter +1
		end
		
		local functionArg =
		{
			argType = varType,
			argName = varName,
			specialCaseArray = arr
		}
		table.insert(functionParams,functionArg)
		
	end
	
	return  functionParams
end

local function createFunctionDef(functionDef)
	local matchStr = "(.*)(%s*)"..Mocking_details.visability.."(%s*)(%w*)(%s*)(%((.*)%))"
	--local ret,s1,s2,name,s3,b1,params,b2 = string.match(functionDef,"(.*)(%s*)GL_APIENTRY(%s*)(%w*)(%s*)(%((.*)%))")
	local ret,s1,s2,name,s3,b1,params,b2 = string.match(functionDef,matchStr)
	local sepParams = split_str(params)
	local functionParams = splitTypesAndNames(sepParams)
	ret = trim_string(ret)
	
	
	local totalParams = #sepParams
	local paramWrapper = nil
	if(totalParams > Mocking_details.gmockMaxParams)then
		
		local wrapperName = name .. Mocking_details.paramWrapperPostFix 
		
		paramWrapper = "struct " .. wrapperName .."\n{\n"

		for  i = Mocking_details.gmockMaxParams,totalParams do
			paramWrapper = paramWrapper .. "\t".. functionParams[i].argType .. " " .. functionParams[i].argName ..";\n"
		end
		
		paramWrapper = paramWrapper .."};"
		
		table.insert(extraParamStructs,paramWrapper)
	end
	
	local t = 
	{
		returnType = ret,
		functionName = name,
		paramCount = #sepParams,
		paramsTable = functionParams,
	}
	return t
end

local function grab_core_functions(file)
	local f = io.open(file,"r")
	if(f ==nil)then print("Unable to find header " .. file)end
	assert(f)
	local str = f:read("*a")
	f:close()
	
	cont = true
 	funcs = {}
 	
	while (cont)do
		funcStartBegin , funcStartEnd = string.find(str,Mocking_details.func_start)
		if( funcStartBegin and funcStartEnd )then
			k , semiColon = string.find(str,";",funcStartEnd)
			def = string.sub(str,funcStartEnd+1,semiColon-1)
			table.insert(funcs,createFunctionDef(def))
			len = string.len(str)
			str = string.sub(str,semiColon+1,len)
		else
			cont = false
		end
	end
	return funcs
end

local function grab_ext_functions(file)

	f = io.open(file,"r")
	local str = f:read("*a")
	if(f ==nil)then print("Unable to find header " .. file) assert(false) end
	assert(f)
	f:close()

 	local allExtensions = {}
 	local tempFileWithData = os.tmpname ()
	
 	local matcher = "%#ifndef%s*([%w_]*)%s*%#define%s*[%w_]*%s*%d+%s*%#ifdef%s*GL_GLEXT_PROTOTYPES"
 	local defineGuard = string.match(str,matcher) --nil if not found
	local endIfEnd = 1

 	while(defineGuard ~=nil) do
		local matcherStart, matcherEnd = string.find(str,matcher,endIfEnd)
		local endIfStart
		
		endIfStart,endIfEnd = string.find(str,"%#endif",matcherEnd)
	
		local stripedData = string.sub(str,matcherEnd+1,endIfStart-1)

		local tempFile = io.open(tempFileWithData,"w")
		tempFile:write(stripedData)
		tempFile:close()
		
		local ext =
 		{
 			guard=defineGuard,
 			funcTable = grab_core_functions(tempFileWithData)
 		}
	
		table.insert(allExtensions,ext)
		defineGuard = string.match(str,matcher,endIfEnd)
	end
	os.remove(tempFileWithData)
	
	return allExtensions

end

local function functionTypesAndNamesEnclosedByBraces_amountLimitedByGmock(def)
	local str ="("
	
	local function addArgTypeAndName(index,definition)
		if(index~=1)then 
			str = str ..","
		end
		str = str .. definition.paramsTable[index].argType .. " " .. definition.paramsTable[index].argName
		str = str .." " .. definition.paramsTable[index].specialCaseArray
	end
	
	if(def.paramCount > Mocking_details.gmockMaxParams) then
		for  i = 1 ,Mocking_details.gmockMaxParams -1 do
			addArgTypeAndName(i,def)
		end
		str = str .. "," ..def.functionName .. Mocking_details.paramWrapperPostFix .." wrapper"
	else
		for  i = 1 ,def.paramCount do
			addArgTypeAndName(i,def)
		end
	end
	
	str = str .. ")"
	return str
end

local function functionTypesAndNamesEnclosedByBraces(def)
	local str ="("
	for i,v in pairs(def.paramsTable)do
		str = str .. v.argType .. " " ..v.argName .." " .. v.specialCaseArray
		if(i ~= #def.paramsTable)then 
			str = str .."," 
		end
		
	end
	str = str ..")"
	return str
end

local function proxyFunctionImpString(def)

	--all parameters and types
	--return name(type name)
	--local str = "GL_APICALL " .. def.returnType .." GL_APIENTRY " .. def.functionName 
	local str = Mocking_details.func_start .." " .. def.returnType .." " .. Mocking_details.visability .." ".. def.functionName 
	
	
	str = str .. functionTypesAndNamesEnclosedByBraces(def)
	str = str .."\n{\n"
	
	--more parameters than allowed with gmock, so create the wrapper instance and intialise
	if(def.paramCount > Mocking_details.gmockMaxParams)then
		str = str .."\t" .. def.functionName .. Mocking_details.paramWrapperPostFix .. " wrapper = {"
		for  i = Mocking_details.gmockMaxParams ,def.paramCount do
			if(i~=Mocking_details.gmockMaxParams)then 
				str = str .. ","
			end
			str = str .." " .. def.paramsTable[i].argName
		end
		str = str .. "};\n"
	end
	
	--parameter amount limited by gmock
	--return instance->func(params)
	str = str .. "\treturn " .. Mocking_details.coreClassName .."::" ..Mocking_details.coreImpInstanceName .."->" .. def.functionName .."("
	
	local function addArgName(index,definition)
		if(index~=1)then 
			str = str .. ","
		end
		str = str .. definition.paramsTable[index].argName
	end
	
	if(def.paramCount > Mocking_details.gmockMaxParams) then
		for  i = 1 ,Mocking_details.gmockMaxParams -1 do
			addArgName(i,def)
		end
		str = str ..",wrapper"
	else
		for  i = 1 ,def.paramCount do
			addArgName(i,def)
		end
	end
	
	str = str .. ");\n}\n"

	return str
end

local function mockMethodString(def)
	
	local noOfParams = def.paramCount
	if(def.paramCount > Mocking_details.gmockMaxParams)then noOfParams= Mocking_details.gmockMaxParams end
	
	local mockStr = "\tMOCK_METHOD".. noOfParams .. "(" .. def.functionName ..",".. def.returnType 
	mockStr = mockStr .. functionTypesAndNamesEnclosedByBraces_amountLimitedByGmock(def)
	mockStr = mockStr .. " );\n"
	
	return mockStr
end

local function writeCoreMockClass(funcs,allExts)
	local ofile = io.open(Mocking_details.coreMockHeader,"w")
	
	headerGuard(ofile,Mocking_details.coreMockHeaderGuard)
	includeHeader(ofile,Mocking_details.coreInterfaceHeader,false)
	includeHeader(ofile,"gmock/gmock.h",false)
	
	ofile:write("struct " ..Mocking_details.coreMockClassName .." : public " ..Mocking_details.coreClassName .. "\n{\n")
	
	for i , v in pairs(funcs) do 
		ofile:write( mockMethodString(v) )
	end

	ofile:write("\n\n\t/*extension function mocks*/\n\n")

	for i0 , v0 in pairs(allExts) do 
		for i1 , v1 in pairs(v0.funcTable) do 
			ofile:write( mockMethodString(v1) )
		end
	end

	
	ofile:write("};\n")
	ofile:write("\n#endif \n")
	ofile:close()
end

local function write_core_header_and_source(funcs,extFuncs)
	local ofile = io.open(Mocking_details.coreInterfaceHeader,"w")
	headerGuard(ofile,Mocking_details.coreHeaderGuard)
	includeHeader(ofile,Mocking_details.gl_dir .."/" ..Mocking_details.coreHeader,true)

	--write wrappers struct im extern "C"{}
	ofile:write("extern \"C\"\n{\n")
	for i,v in pairs(extraParamStructs)do
		ofile:write(v .."\n")
	end
	ofile:write("}\n")
	
	ofile:write("struct " ..Mocking_details.coreClassName .. "\n")
	ofile:write("{\n")
	ofile:write("\tvirtual ~"..Mocking_details.coreClassName .."(){}\n")
	ofile:write("\tstatic " ..Mocking_details.coreClassName .. "* imp;\n")
	
	local function writeVirtualMethod(def)
		ofile:write("\tvirtual " .. def.returnType .." " .. def.functionName )
		ofile:write( functionTypesAndNamesEnclosedByBraces_amountLimitedByGmock(def) .." =0;\n")
	end
	
	for i , v in pairs(funcs) do 
		writeVirtualMethod(v)
	end
	
	ofile:write("\n\n\t/*extension functions*/\n\n")
		
	for i0 , v0 in pairs(extFuncs) do 
		for i1 , v1 in pairs(v0.funcTable) do 
			writeVirtualMethod(v1)
		end
	end

	ofile:write("};\n")
	
	ofile:write("\n#endif \n")
	ofile:close()
	
	ofile = io.open(Mocking_details.coreInterfaceSource,"w")
	includeHeader(ofile,Mocking_details.coreInterfaceHeader,false)
	ofile:write(Mocking_details.coreClassName .."* " ..Mocking_details.coreClassName .."::" ..Mocking_details.coreImpInstanceName .. " = 0;\n")
	ofile:close()
end

local function write_extImp(allExtFunc)
	local ofile = io.open(Mocking_details.extensionImpFile,"w")
	includeHeader(ofile,Mocking_details.gl_dir .."/" .. Mocking_details.extensionHeader,true)
	includeHeader(ofile,Mocking_details.coreInterfaceHeader,false)
	
	ofile:write("extern \"C\"\n{\n")
	local tableIndex =1
	while ( tableIndex <= #allExtFunc) do
		ofile:write("#ifdef " .. allExtFunc[tableIndex].guard .."\n")
		for i , v in pairs(allExtFunc[tableIndex].funcTable) do 
		local str = proxyFunctionImpString(v)
			ofile:write( str )
		end
		ofile:write("#endif\n\n")
		tableIndex = tableIndex+1
	end
	ofile:write("}\n")
	ofile:close()
end


local function write_coreImp(func)
	local ofile = io.open(Mocking_details.coreImpFile,"w")
	includeHeader(ofile,Mocking_details.gl_dir .. "/" ..Mocking_details.coreHeader,true)
	includeHeader(ofile,Mocking_details.coreInterfaceHeader,false)
	ofile:write("extern \"C\"{\n")
	for i , v in pairs(func) do 
		local str = proxyFunctionImpString(v)
		ofile:write( str )
	end
	ofile:write("}\n")
	ofile:close()
end

function mock_gl(gl_mocking_details)

	Mocking_details = gl_mocking_details
	
	local funcs = grab_core_functions("./"..gl_mocking_details.dir .."/" ..Mocking_details.gl_dir .."/" ..Mocking_details.coreHeader)
	local extfuncs = grab_ext_functions("./" ..gl_mocking_details.dir .."/" ..Mocking_details.gl_dir .."/" ..Mocking_details.extensionHeader)
	
	os.execute("cd ./"..gl_mocking_details.dir)
	write_core_header_and_source(funcs,extfuncs)
	writeCoreMockClass(funcs,extfuncs)
	write_coreImp(funcs)

	os.execute("cd ..")
end

